<script lang="ts">
import {
  ref,
  defineComponent,
  onUnmounted,
  watch,
  nextTick,
  PropType,
} from "vue";
import { MenuSetting, Pos } from "./types";
export default defineComponent({
  name: "contextMenu",
  props: {
    appendToBody: {
      type: Boolean,
      default: true,
    },
    menuWidth: {
      type: Number,
      default: 240,
    },
    menuList: {
      type: Array as PropType<MenuSetting[]>,
      required: true,
    },
    el: {
      type: Object as PropType<HTMLElement>,
      required: true,
    },
    injectCloseListener: {
      type: Boolean,
      default: true,
    },
    pos: {
      type: Object as PropType<Pos>,
      default: () => ({
        x: 0,
        y: 0,
      }),
    },
  },
  setup(props) {
    const subLeft = ref(0);
    const subTop = ref(0);
    const hoverFlag = ref(false);
    const menuTop = ref(0);
    const menuLeft = ref(0);
    const showMenu = ref(false);
    const MenuWrapper = ref();

    const handleMenuItemClick = (item: any, event: MouseEvent) => {
      if (item.disabled) return;
      if (item.fn && typeof item.fn === "function") {
        item.fn();
      }
      showMenu.value = false;
    };

    const handleMenuMouseEnter = async (event: MouseEvent, item: any) => {
      if (item.children && !item.disabled) {
        hoverFlag.value = true;
        await nextTick();
        const el = event.currentTarget as HTMLElement;
        if (!el) return;
        const { offsetWidth } = el;
        const subEl = el.querySelector(".menu_sub__wrapper") as HTMLElement;
        if (!subEl) return;
        const { offsetWidth: subOffsetWidth, offsetHeight: subOffsetHeight } =
          subEl;
        const { innerWidth: windowWidth, innerHeight: windowHeight } = window;
        const { top, left } = el.getBoundingClientRect();
        if (left + offsetWidth + subOffsetWidth > windowWidth - 5) {
          subLeft.value = left - subOffsetWidth;
        } else {
          subLeft.value = left + offsetWidth;
        }
        if (top + subOffsetHeight > windowHeight - 5) {
          subTop.value = windowHeight - subOffsetHeight;
        } else {
          subTop.value = top + 5;
        }
      }
    };

    const handleSubMenuItemClick = (subItem: any, event: MouseEvent) => {};

    const show = async () => {
      showMenu.value = true;
      await nextTick();
      const { x, y } = props.pos;
      const { innerWidth: windowWidth, innerHeight: windowHeight } = window;
      const menu = MenuWrapper.value;
      const menuHeight = menu.offsetHeight;
      const menuWidth = props.menuWidth || 240;
      menuLeft.value =
        x + menuWidth + 1 > windowWidth ? windowWidth - menuWidth - 5 : x + 1;
      menuTop.value =
        y + menuHeight + 1 > windowHeight
          ? windowHeight - menuHeight - 5
          : y + 1;
    };

    const close = () => {
      showMenu.value = false;
    };

    const listenerFn = (e: MouseEvent) => {
      if (MenuWrapper.value && !MenuWrapper.value.contains(e.currentTarget)) {
        showMenu.value = false;
        document.oncontextmenu = null;
      }
    };

    watch(
      () => props.injectCloseListener,
      (val) => {
        if (val) {
          document.addEventListener("mousedown", listenerFn);
        } else {
          document.removeEventListener("mousedown", listenerFn);
        }
      },
      {
        immediate: true,
      }
    );

    onUnmounted(() => {
      document.removeEventListener("mousedown", listenerFn);
    });

    return {
      subLeft,
      subTop,
      hoverFlag,
      menuTop,
      menuLeft,
      showMenu,
      MenuWrapper,
      handleMenuItemClick,
      handleMenuMouseEnter,
      handleSubMenuItemClick,
      show,
      close,
    };
  },
});
</script>

<template>
  <teleport to="body" :disabled="!appendToBody">
    <div
      v-if="showMenu"
      ref="MenuWrapper"
      class="menu_wrapper"
      :style="{
        width: `${menuWidth}px`,
        top: `${menuTop}px`,
        left: `${menuLeft}px`,
      }"
    >
      <template v-for="(item, index) in menuList" :key="index">
        <div
          v-if="!item.hidden && !item.line"
          class="menu_item"
          :class="{
            menu_item__border: item.border,
          }"
          @mousedown.stop="handleMenuItemClick(item, $event)"
          @mouseenter="handleMenuMouseEnter($event, item)"
        >
          <div class="menu_item__icon mr-1">
            <i v-show="item.icon" :class="item.icon"></i>
          </div>
          <div class="menu_item__label mr-1 ellipsis">{{ item.label }}</div>
          <div
            class="menu_item__icon"
            v-if="item.children && item.children.length > 0"
          >
            <i class="icon-angle-down"></i>
          </div>
          <div
            v-show="hoverFlag"
            v-if="item.children && item.children.length > 0"
            class="menu_sub__wrapper"
            :style="{ top: `${subTop}px`, left: `${subLeft}px` }"
          >
            <template
              v-for="(subItem, subIndex) in item.children"
              :key="subIndex"
            >
              <div
                :class="['menu_item', subItem.disabled && 'disabled']"
                @mousedown.stop="handleSubMenuItemClick(subItem, $event)"
              >
                <div class="menu_item__icon mr-1">
                  <i v-show="subItem.icon" :class="subItem.icon"></i>
                </div>
                <div class="menu_item__label">{{ subItem.label }}</div>
              </div>
            </template>
          </div>
        </div>
      </template>
    </div>
  </teleport>
</template>

<style lang="scss" scoped>
.menu_wrapper,
.menu_sub__wrapper {
  --menu-background: var(--music-main-color);
  --menu-boxShadow: 2px 3px 10px var(--music-color-2);
  --menu-padding: 10px 0;
  --menu-borderRadius: 6px;
  --menu-item-height: 40px;
  --menu-item-padding: 0 20px;
  --menu-item-fontsize: 16px;
  --menu-item-iconSize: 20px;
  --menu-item-iconColor: var(--music-link-color);
  --menu-item-hoverBackground: var(--music-color-2);
  --menu-item-color: var(--music-link-hover-color);
  --menu-lineMargin: 5px 0;
  --menu-border: 2px solid var(--music-color-4);
}
.menu_wrapper,
.menu_sub__wrapper {
  position: fixed;
  width: 240px;
  background: var(--menu-background);
  box-shadow: var(--menu-boxShadow);
  padding: var(--menu-padding);
  border-radius: var(--menu-borderRadius);
  z-index: 99999;
}
.menu_item {
  line-height: var(--menu-item-height);
  padding: var(--menu-item-padding);
  color: var(--menu-item-color);
  font-size: var(--menu-item-fontsize);
  display: flex;
  align-items: center;
  cursor: pointer;
  &:hover {
    background-color: var(--menu-item-hoverBackground);
  }
  .menu_item__icon {
    width: 20px;
    font-size: 16px;
    transform: scale(1.2);
  }
  .menu_item__label {
    flex: 1;
  }
}
.menu_item__border {
  border-bottom: var(--menu-border);
}
.menu_sub__wrapper {
  position: fixed;
  visibility: hidden;
  width: 200px;
}
.menu_item:hover .menu_sub__wrapper {
  visibility: visible;
}
</style>
